// 版权所有2020 etcd作者
// 
// 根据Apache许可证2.0版（以下简称“许可证”）获得许可；
// 除非遵守许可证，否则不得使用此文件。
// 您可以通过
// 
// www.apache.org/licenses/LICENSE-2.0 
// 
// 除非适用法律要求或书面同意，否则根据许可证分发的软件
// 按“原样”分发，
// 无任何明示或暗示的保证或条件。
// 请参阅许可证，了解管理许可的特定语言和
// 许可证下的限制。

package version

import (
	"github.com/coreos/go-semver/semver"
	"go.etcd.io/etcd/api/v3/version"
	"go.uber.org/zap"
)

type DowngradeInfo struct {
	// TargetVersion为目标降级版本，如果集群未降级，
	// targetVersion将是一个空字符串
	TargetVersion string `json:"target-version"`
	// Enabled表示群集是否已启用降级
	Enabled bool `json:"enabled"`
}

func (d *DowngradeInfo) GetTargetVersion() *semver.Version {
	return semver.Must(semver.NewVersion(d.TargetVersion))
}

// isValidDowngrade验证群集是否可以从verFrom降级到verTo 
func isValidDowngrade(verFrom *semver.Version, verTo *semver.Version) bool {
	return verTo.Equal(*allowedDowngradeVersion(verFrom))
}

// MustDetectDowngrade将检测到加入群集的本地服务器，而该本地服务器没有降级支持它的版本。
func MustDetectDowngrade(lg *zap.Logger, sv, cv *semver.Version) {
	// 只保留主要部分。次要版本与集群版本
	sv = &semver.Version{Major: sv.Major, Minor: sv.Minor}

	// 如果集群禁用降级，请根据确定的集群版本检查本地版本。
	// 当本地版本不低于集群版本时验证通过
	if cv != nil && sv.LessThan(*cv) {
		lg.Panic(
			"invalid downgrade; server version is lower than determined cluster version",
			zap.String("current-server-version", sv.String()),
			zap.String("determined-cluster-version", version.Cluster(cv.String())),
		)
	}
}

func allowedDowngradeVersion(ver *semver.Version) *semver.Version {
	// Todo:处理从更高主版本降级（例如从v4.0降级到v3.x）的情况
	return &semver.Version{Major: ver.Major, Minor: ver.Minor - 1}
}

// IsValidVersionChange检查版本有效更改时的两个场景：
// 1。降级：群集版本比本地版本高1个次要版本，
// 群集版本应更改。
// 2。集群启动：当并非所有成员版本都可用时，集群版本
// 设置为MinVersion（3.0），当所有成员都处于较高版本时，集群版本
// 低于本地版本，集群版本应更改
func IsValidVersionChange(cv *semver.Version, lv *semver.Version) bool {
	cv = &semver.Version{Major: cv.Major, Minor: cv.Minor}
	lv = &semver.Version{Major: lv.Major, Minor: lv.Minor}

	if isValidDowngrade(cv, lv) || (cv.Major == lv.Major && cv.LessThan(*lv)) {
		return true
	}
	return false
}
